use ripemd::Ripemd160;
use serde::{Deserialize, Serialize};
use sha2::Digest;

use crate::{
    BlockChainBaseError, Hash160Bytes,
    constants::{HASH160_BYTES_LENGTH, PUBLIC_KEY_BYTES_LENGTH, PUBLIC_KEY_HASH_BYTES_LENGTH},
};

#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct PublicKeyHashBytes(Hash160Bytes);

impl std::fmt::Display for PublicKeyHashBytes {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.to_hex_str())
    }
}

impl std::fmt::Debug for PublicKeyHashBytes {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.to_hex_str())
    }
}

impl PublicKeyHashBytes {
    pub fn new(hash: Hash160Bytes) -> Self {
        Self(hash)
    }

    pub fn new_null() -> Self {
        Self([0; HASH160_BYTES_LENGTH])
    }

    pub fn is_null(&self) -> bool {
        self.0 == [0; HASH160_BYTES_LENGTH]
    }

    pub fn as_bytes_ref(&self) -> &Hash160Bytes {
        &self.0
    }

    pub fn to_hex_str(&self) -> String {
        hex::encode(self.0)
    }

    pub fn from_hex_str(s: &str) -> Result<Self, BlockChainBaseError> {
        let bytes = hex::decode(s)?;
        if bytes.len() != PUBLIC_KEY_HASH_BYTES_LENGTH {
            return Err(BlockChainBaseError::PubkeyHashBytesHexStrLenInvalid);
        }

        Ok(Self(bytes.try_into().unwrap()))
    }
}

// The length of a public key is 33 bytes. It's too large so can not use Serialize derive.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PublicKeyBytes([u8; PUBLIC_KEY_BYTES_LENGTH]);

impl std::fmt::Display for PublicKeyBytes {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.to_hex_str())
    }
}

impl PublicKeyBytes {
    pub fn new(bytes: [u8; PUBLIC_KEY_BYTES_LENGTH]) -> Self {
        Self(bytes)
    }

    pub fn from_vec(vec: Vec<u8>) -> Result<Self, BlockChainBaseError> {
        if vec.len() < PUBLIC_KEY_BYTES_LENGTH {
            return Err(BlockChainBaseError::PubkeyBytesVecLenTooShort);
        }

        let mut array = [0u8; PUBLIC_KEY_BYTES_LENGTH];
        array.copy_from_slice(&vec[..PUBLIC_KEY_BYTES_LENGTH]);

        Ok(PublicKeyBytes(array))
    }

    pub fn from_slice(slice: &[u8; PUBLIC_KEY_BYTES_LENGTH]) -> Self {
        Self(*slice)
    }

    pub fn from_hex_str(s: &str) -> Result<Self, BlockChainBaseError> {
        let bytes = hex::decode(s)?;
        if bytes.len() != PUBLIC_KEY_BYTES_LENGTH {
            return Err(BlockChainBaseError::PubkeyBytesHexStrLenInvalid);
        }

        Ok(Self(bytes.try_into().unwrap()))
    }

    pub fn to_hex_str(&self) -> String {
        hex::encode(self.0)
    }

    pub fn length(&self) -> usize {
        PUBLIC_KEY_BYTES_LENGTH
    }

    pub fn as_bytes_ref(&self) -> &[u8; PUBLIC_KEY_BYTES_LENGTH] {
        &self.0
    }

    pub fn calc_ripemd160_hash_raw(&self) -> Hash160Bytes {
        let ripemd160_hash = Ripemd160::digest(self.0);
        let hash_array: [u8; 20] = ripemd160_hash.as_slice().try_into().unwrap();
        hash_array
    }

    pub fn to_public_key_hash(&self) -> PublicKeyHashBytes {
        PublicKeyHashBytes::new(self.calc_ripemd160_hash_raw())
    }
}

impl Serialize for PublicKeyBytes {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        use serde::ser::SerializeTuple;
        let mut seq = serializer.serialize_tuple(PUBLIC_KEY_BYTES_LENGTH)?;
        for byte in self.0.iter() {
            seq.serialize_element(byte)?;
        }
        seq.end()
    }
}

impl<'de> Deserialize<'de> for PublicKeyBytes {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        struct PublicKeyBytesVisitor;

        impl<'de> serde::de::Visitor<'de> for PublicKeyBytesVisitor {
            type Value = PublicKeyBytes;

            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
                formatter
                    .write_str(format!("an array of {} bytes", PUBLIC_KEY_BYTES_LENGTH).as_str())
            }

            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
            where
                A: serde::de::SeqAccess<'de>,
            {
                let mut bytes = [0u8; PUBLIC_KEY_BYTES_LENGTH];
                for (i, it) in bytes.iter_mut().enumerate().take(PUBLIC_KEY_BYTES_LENGTH) {
                    *it = seq.next_element()?.ok_or_else(|| {
                        serde::de::Error::invalid_length(
                            i,
                            &format!("array of length {}", PUBLIC_KEY_BYTES_LENGTH).as_str(),
                        )
                    })?;
                }
                Ok(PublicKeyBytes(bytes))
            }
        }

        deserializer.deserialize_tuple(PUBLIC_KEY_BYTES_LENGTH, PublicKeyBytesVisitor)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::constants::PUBLIC_KEY_BYTES_LENGTH;

    #[test]
    fn test_key_pair_serialization() {
        let (bob_secret_key, bob_public_key) = bc_util::generate_secp256k1_keypair();
        let secret = bob_secret_key.secret_bytes();
        let pubkey = PublicKeyBytes::new(bob_public_key.serialize());

        assert_eq!(
            secret.len(),
            crate::constants::PRIVATE_KEY_BYTES_LENGTH,
            "Secret key should be {} bytes long, but it is {} bytes",
            crate::constants::PRIVATE_KEY_BYTES_LENGTH,
            secret.len()
        );
        assert_eq!(
            pubkey.length(),
            crate::constants::PUBLIC_KEY_BYTES_LENGTH,
            "Public key should be {} bytes long, but it is {} bytes",
            crate::constants::PUBLIC_KEY_BYTES_LENGTH,
            pubkey.length()
        );
    }

    #[test]
    fn test_serde_bincode_pubkey_bytes() {
        #[rustfmt::skip]
        let data: [u8; PUBLIC_KEY_BYTES_LENGTH] = [
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
            17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
            33
        ];
        let pubkey = PublicKeyBytes::new(data);
        let serialized = bincode::serialize(&pubkey).unwrap();
        let deserialized: PublicKeyBytes = bincode::deserialize(&serialized).unwrap();
        assert_eq!(pubkey, deserialized);
    }

    #[test]
    fn test_public_key_bytes_new() {
        let bytes = [0u8; PUBLIC_KEY_BYTES_LENGTH];
        let pubkey = PublicKeyBytes::new(bytes);
        assert_eq!(pubkey.0, bytes);
    }

    #[test]
    fn test_public_key_bytes_from_vec() {
        let vec = vec![1u8; PUBLIC_KEY_BYTES_LENGTH];
        let pubkey = PublicKeyBytes::from_vec(vec).unwrap();
        let expected = [1u8; PUBLIC_KEY_BYTES_LENGTH];
        assert_eq!(pubkey.0, expected);
    }

    #[test]
    fn test_public_key_bytes_from_vec_too_short() {
        let vec = vec![2u8; PUBLIC_KEY_BYTES_LENGTH - 1];
        let result = PublicKeyBytes::from_vec(vec);
        assert_eq!(result, Err(BlockChainBaseError::PubkeyBytesVecLenTooShort));
    }

    #[test]
    fn test_public_key_bytes_from_hex_str() {
        let hex_str = "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021";
        let pubkey = PublicKeyBytes::from_hex_str(hex_str).unwrap();
        let expected = [
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
            25, 26, 27, 28, 29, 30, 31, 32, 33,
        ];
        assert_eq!(pubkey.0, expected);
    }

    #[test]
    fn test_public_key_bytes_from_hex_str_invalid_length() {
        let hex_str = "010203";
        let result = PublicKeyBytes::from_hex_str(hex_str);
        assert_eq!(
            result,
            Err(BlockChainBaseError::PubkeyBytesHexStrLenInvalid)
        );
    }

    #[test]
    fn test_public_key_bytes_to_hex_str() {
        let bytes = [
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
            25, 26, 27, 28, 29, 30, 31, 32, 33,
        ];
        let pubkey = PublicKeyBytes::new(bytes);
        let hex_str = pubkey.to_hex_str();
        assert_eq!(
            hex_str,
            "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021"
        );
    }

    #[test]
    fn test_public_key_bytes_len() {
        let bytes = [0u8; PUBLIC_KEY_BYTES_LENGTH];
        let pubkey = PublicKeyBytes::new(bytes);
        assert_eq!(pubkey.length(), PUBLIC_KEY_BYTES_LENGTH);
    }

    #[test]
    fn test_public_key_bytes_as_bytes_ref() {
        let bytes = [1u8; PUBLIC_KEY_BYTES_LENGTH];
        let pubkey = PublicKeyBytes::new(bytes);
        assert_eq!(pubkey.as_bytes_ref(), &bytes);
    }

    #[test]
    fn test_public_key_bytes_calc_ripemd160_hash_raw() {
        let bytes = [0u8; PUBLIC_KEY_BYTES_LENGTH];
        let pubkey = PublicKeyBytes::new(bytes);
        let hash = pubkey.calc_ripemd160_hash_raw();
        assert_eq!(hash.len(), 20);
    }

    #[test]
    fn test_public_key_bytes_to_pub_key_hash() {
        let bytes = [0u8; PUBLIC_KEY_BYTES_LENGTH];
        let pubkey = PublicKeyBytes::new(bytes);
        let pub_key_hash = pubkey.to_public_key_hash();
        assert_eq!(pub_key_hash.as_bytes_ref().len(), 20);
    }

    #[test]
    fn test_public_key_hash_bytes_new() {
        let hash = [0u8; 20];
        let pub_key_hash = PublicKeyHashBytes::new(hash);
        assert_eq!(pub_key_hash.0, hash);
    }

    #[test]
    fn test_public_key_hash_bytes_as_bytes_ref() {
        let hash = [1u8; 20];
        let pub_key_hash = PublicKeyHashBytes::new(hash);
        assert_eq!(pub_key_hash.as_bytes_ref(), &hash);
    }

    #[test]
    fn test_public_key_hash_bytes_to_hex_str() {
        let hash = [
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
        ];
        let pub_key_hash = PublicKeyHashBytes::new(hash);
        let hex_str = pub_key_hash.to_hex_str();
        assert_eq!(hex_str, "0102030405060708090a0b0c0d0e0f1011121314");
    }

    #[test]
    fn test_public_key_hash_bytes_from_hex_str() {
        let hex_str = "0102030405060708090a0b0c0d0e0f1011121314";
        let pub_key_hash = PublicKeyHashBytes::from_hex_str(hex_str).unwrap();
        let expected = [
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
        ];
        assert_eq!(pub_key_hash.0, expected);
    }

    #[test]
    fn test_public_key_hash_bytes_from_hex_str_invalid_length() {
        let hex_str = "010203";
        let result = PublicKeyHashBytes::from_hex_str(hex_str);
        assert_eq!(
            result,
            Err(BlockChainBaseError::PubkeyHashBytesHexStrLenInvalid)
        );
    }
}
